package mosdi.paa;

import mosdi.fa.AutomatonBasedTextModel;
import mosdi.matching.SundayMatcher;
import mosdi.util.Log;

public class SundayPAA extends TextBasedPAA {

	private int alphabetSize;
	private int[] pattern;
	private int stateCount;
	private int valueCount;
	private int maxCost;
	private int[] shift;
	
	public SundayPAA(AutomatonBasedTextModel textModel, int textLength, int[] pattern) {
		super(textModel);
		this.alphabetSize = textModel.getAlphabetSize();
		this.pattern = pattern;
		this.shift = SundayMatcher.computeShiftTable(alphabetSize, pattern);
		this.stateCount = (pattern.length+2) * alphabetSize + 1;
		if (pattern.length>10) throw new IllegalArgumentException("Pattern to long.");
		this.maxCost = textLength * (pattern.length+1);
		this.valueCount = (maxCost+1) * factorial(pattern.length);
		Log.println(Log.Level.DEBUG, "Max cost: "+maxCost);
		Log.println(Log.Level.DEBUG, "Size of state space: "+stateCount);
		Log.println(Log.Level.DEBUG, "Size of value space: "+valueCount);
		double runtimeEstimate = ((double)valueCount) * stateCount * textLength * alphabetSize;
		double runtimeEstimateSeconds = runtimeEstimate * 8.2e-08;
		Log.println(Log.Level.DEBUG, "Runtime estimate: "+(runtimeEstimate*1e-6)+" Mio. operations ("+runtimeEstimateSeconds+" seconds)");
		
//		int[] skipVector = {5,4,3,2,1};
//		int x = encodeValue(120, skipVector);
//		Log.println(Log.Level.DEBUG, "x: "+x);
//		Log.println(Log.Level.DEBUG, "Cost: "+decodeValueToCost(x));
//		Log.println(Log.Level.DEBUG, "skipVector: "+Arrays.toString(decodeValueToSkipVector(x)));

//		int x = encodeState(-1, 3);
//		Log.println(Log.Level.DEBUG, "x: "+x);
//		Log.println(Log.Level.DEBUG, "Position: "+decodeStateToPosition(x));
//		Log.println(Log.Level.DEBUG, "Char: "+decodeStateToCharacter(x));
	}
	
	public static int factorial(int x) {
		int result = 1;
		for (int i=2; i<=x; ++i) result*=i;
		return result;
	}
	
	@Override
	public double emissionProbability(int state, int emission) {
		return 1.0;
	}

	@Override
	public int getEmissionCount() {
		return 1;
	}

	@Override
	public int getStartState() {
		return 0;
	}

	@Override
	public int getStartValue() {
		return 0;
	}

	@Override
	public int getStateCount() {
		return stateCount;
	}

	@Override
	public int getValueCount() {
		return valueCount;
	}

	private int encodeValue(int cost, int[] skipVector) {
		assert((cost>=0) && (cost<=maxCost));
		int result = cost;
		int factor = maxCost+1;
		int rangeSize = pattern.length;
		for (int i=0; i<pattern.length-1; ++i) {
			assert((skipVector[i]>=0) && (skipVector[i]<pattern.length-i));
			result+=factor*skipVector[i];
			factor*=rangeSize;
			rangeSize-=1;
		}
		return result;
	}
	
	private int decodeValueToCost(int value) {
		return value % (maxCost+1);
	}
	
	private int[] decodeValueToSkipVector(int value) {
		int[] skipVector = new int[pattern.length-1];
		value /= (maxCost+1);
		int rangeSize = pattern.length;
		for (int i=0; i<pattern.length-1; ++i) {
			skipVector[i]=value%rangeSize;
			value/=rangeSize;
			rangeSize-=1;
		}
		return skipVector;
	}
	
	private int encodeState(int position, int character) {
		assert((position>=-1) && (position<=pattern.length));
		assert((character>=0) && (character<alphabetSize));
		return 1 + character + (position+1)*alphabetSize;
	}
	
	private boolean isStartState(int state) {
		return state==0;
	}
	
	private int decodeStateToPosition(int state) {
		return ((state-1)/alphabetSize)-1;
	}

	private int decodeStateToCharacter(int state) {
		return (state-1)%alphabetSize;
	}

	private void move(int[] skipVector, int l) {
		int i=0;
		for (; i<skipVector.length-l; ++i) skipVector[i]=skipVector[i+l];
		for (; i<skipVector.length; ++i) skipVector[i]=0;
	}

	private void update(int[] skipVector, int sigma, int k) {
		for (int j=0; j<skipVector.length; ++j) {
			if ((j<k) && (sigma!=pattern[k-j])) skipVector[j]=k-j;
		}
	}
	
	@Override
	public int performOperation(int state, int value, int emission) {
		if (isStartState(state)) return value;
		int k = decodeStateToPosition(state);
		int sigma = decodeStateToCharacter(state);
		int cost = decodeValueToCost(value);
		int[] skipVector = decodeValueToSkipVector(value);
		if (k<pattern.length-1) {
			update(skipVector,sigma,k);
			return encodeValue(cost, skipVector);
		} else if (k==pattern.length-1) {
			update(skipVector,sigma,k);
			cost = Math.min(cost+pattern.length-skipVector[0], maxCost);
			return encodeValue(cost, skipVector);
		} else {
			assert(k==pattern.length);
			move(skipVector,shift[sigma]);
			return performOperation(encodeState(k-shift[sigma],sigma),encodeValue(Math.min(cost+1, maxCost),skipVector),emission);
		}
	}

	@Override
	public int transitionTarget(int state, int sigma) {
		if (isStartState(state)) return encodeState(0, sigma);
		int k = decodeStateToPosition(state);
		int sigmaP = decodeStateToCharacter(state);
		if (k<pattern.length) {
			return encodeState(k+1, sigma);
		} else {
			assert(k==pattern.length);
			return encodeState(k-shift[sigmaP]+1, sigma);
		}
	}

	public double[] costDistribution(double[] valueDistribution) {
		double[] costDistribution = new double[maxCost+1];
		for (int value=0;value<valueCount;++value) {
			costDistribution[decodeValueToCost(value)]+=valueDistribution[value];
		}
		return costDistribution;
	}
	
//	public int computeCost(int[] text) {
//		int state = getStartState();
//		int value = getStartValue();
//		for (int c : text) {
//			state = transitionTarget(state, c);
//			value = performOperation(state, value, 0);
//			Log.println(Log.Level.DEBUG, "State/Position: "+decodeStateToPosition(state));
//			Log.println(Log.Level.DEBUG, "State/Char: "+alphabet.get(decodeStateToCharacter(state)));
//			Log.println(Log.Level.DEBUG, "Value/Cost: "+decodeValueToCost(value));
//			Log.println(Log.Level.DEBUG, "Value/Vector: "+Arrays.toString(decodeValueToSkipVector(value)));
//			Log.println(Log.Level.DEBUG, "----------");
//		}
//		return decodeValueToCost(value);
//	}

	
}